package com.gc.iotools.stream.is;

/*
 * Copyright (c) 2008, Davide Simonetti
 * All rights reserved.
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided that the following 
 * conditions are met:
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Davide Simonetti nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY 
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * <p>
 * Try to copy the data from the underlying <code>InputStream</code> to the
 * <code>OutputStream</code> passed in the constructor. The data copied are
 * similar to the underlying <code>InputStream</code>.
 * </p>
 * <p>
 * Underlying <code>InputStream</code> is closed when method
 * <code>{@linkplain close}</code> is invoked. <code>OutputStream</code> must be
 * closed outside this class. Bytes skipped are copied to the
 * <code>OutputStream</code> while <code>mark</code> and <code>reset</code> are
 * not supported at the moment.
 * </p>
 * <p>
 * When the method <code>{@link close}</code> is invoked all the bytes remaining
 * in the underlying <code>InputStream</code> are copied to the
 * <code>OutputStream</code>. This behavior is different from this class and
 * <code>org.apache.commons.io.input.TeeInputStream</code>.
 * </p>
 * 
 * 
 * @see org.apache.commons.io.input.TeeInputStream
 * @author dvd.smnt
 * @since 1.0.6
 */
public final class TeeInputStreamOutputStream extends InputStream {

	private boolean closed = false;

	private final OutputStream destination;

	private final InputStream source;

	/**
	 * Creates a <code>TeeInputStreamOutputStream</code> and saves its argument,
	 * the input stream <code>source</code> and the output stream
	 * <code>destination</code> for later use.
	 * 
	 * @param source
	 *            The underlying <code>InputStream</code>
	 * @param destination
	 *            Data readed from <code>source</code> is also written to this
	 *            <code>OutputStream</code>.
	 */
	public TeeInputStreamOutputStream(final InputStream source,
			final OutputStream destination) {
		this.source = source;
		this.destination = destination;
	}

	@Override
	public int available() throws IOException {
		return this.source.available();
	}

	/**
	 * This method copy all the data eventually remaining in the internal
	 * <code>InputStream</code> to the <code>OutputStream</code> and closes the
	 * inner <code>InputStream</code>.
	 * 
	 * Internal <code>OutputStream</code> must be closed externally.
	 */
	@Override
	public void close() throws IOException {
		if (!this.closed) {
			IOException e1 = null;
			this.closed = true;
			try {
				int n = 0;
				final byte[] buffer = new byte[8192];
				while ((n = this.source.read(buffer)) > 0) {
					this.destination.write(buffer, 0, n);
				}
			} catch (final IOException e) {
				e1 = new IOException(
						"It's not possible to copy to the OutputStream");
				e1.initCause(e);
			}
			this.source.close();
			if (e1 != null) {
				throw e1;
			}
		}
	}

	@Override
	public void mark(final int readlimit) {
	}

	@Override
	public boolean markSupported() {
		return false;
	}

	@Override
	public int read() throws IOException {
		final int result = this.source.read();
		if (result >= 0) {
			this.destination.write(result);
		}
		return result;
	}

	@Override
	public int read(final byte[] b) throws IOException {
		final int result = this.source.read(b);
		if (result > 0) {
			this.destination.write(b, 0, result);
		}
		return result;
	}

	@Override
	public int read(final byte[] b, final int off, final int len)
			throws IOException {
		final int result = this.source.read(b, off, len);
		if (result > 0) {
			this.destination.write(b, off, result);
		}
		return result;
	}

	@Override
	public void reset() throws IOException {
		throw new IOException("Reset not supported");
	}

	@Override
	public long skip(final long n) throws IOException {
		throw new UnsupportedOperationException(
				"Skip is not (yet) supported by ["
						+ TeeInputStreamOutputStream.class + "]");
	}

}
